#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <syserr.h>
#include <device.h>
#include <utils.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include "device.h"
#include "netcore.h"
#include "printk.h"

#define to_pipe_dev(dev) container_of(dev, struct pipe_device, dev);

static void pipe_mkdir_lpath(const char *lpath)
{
    char path[108];
    int len = strlen(lpath);

    if (len == 0)
        return;

    svc__strscpy(path, lpath, sizeof(path));
    while (len-- > 0)
    {
        if (path[len] == '/')
        {
            path[len] = '\0';
            mkdirs(path);
            break;
        }
    }
}
static int pipe_socket(struct pipe_device *pipe)
{
    struct pipe_cfg *cfg = &pipe->cfg;

    pipe_mkdir_lpath(cfg->lpath);
    return socket_new_local(&pipe->dev, SOCK_DGRAM, cfg->lpath);
}
static int pipe_open(device_t *dev)
{
    struct pipe_device *pipe = to_pipe_dev(dev);
    int sock;

    sock = pipe_socket(pipe);
    if (sock < 0)
        return sock;

    ev__io_init(&dev->io, device_io, sock);
    ev__io_start(dev_loop(dev), &dev->io, EPOLLIN);
    dev_status(dev) = DEVICE_STATUS_CONNECTED;

    return sock;
}

static void pipe_cfg_cp_to(struct pipe_device *pipe, void *dst)
{
    memcpy(dst, &pipe->cfg, sizeof(struct pipe_cfg));
}

static int is_local_changed(struct pipe_device *pipe, const struct pipe_cfg *new)
{
    return strcmp(pipe->cfg.lpath, new->lpath);
}
static int pipe_cfg_cp_from(struct pipe_device *pipe, const struct pipe_cfg *new)
{
    if (ev__is_active(&pipe->dev) && is_local_changed(pipe, new))
        return -EIO;

    memcpy(&pipe->cfg, new, sizeof(*new));
    return 0;
}
static int pipe_ctrl(device_t *dev, u32 cmd, void *args)
{
    struct pipe_device *pipe = to_pipe_dev(dev);

    switch (cmd)
    {
    case DEV_CTRL_CMD_GET_CFG:
        pipe_cfg_cp_to(pipe, args);
        break;
    case DEV_CTRL_CMD_SET_CFG:
        if (memcmp(&pipe->cfg, args, sizeof(struct pipe_cfg)))
            return pipe_cfg_cp_from(pipe, args);
        break;
    default:
        return -ENOSYS;
    }
    return 0;
}

static ssize_t pipe_recvfrom(struct pipe_device *pipe, void *buffer, size_t size)
{
    struct sockaddr_un addr;
    socklen_t len = sizeof(addr);
    ssize_t r;

    memset(&addr, 0, sizeof(addr));
    r = recvfrom(dev_fd(&pipe->dev), buffer, size, 0, (struct sockaddr *)&addr, &len);
    if (r > 0 && len > 0)
        memcpy(pipe->peer.peer_name, addr.sun_path, len - sizeof(addr.sun_family));
    return r;
}
static ssize_t pipe_read(device_t *dev, void *buffer, size_t size)
{
    struct pipe_device *pipe = to_pipe_dev(dev);
    ssize_t r;

    if (!buffer)
    {
        while (recvfrom(dev_fd(dev), &r, sizeof(r), 0, NULL, NULL) > 0) ;
        return size;
    }

    r = pipe_recvfrom(pipe, buffer, size);
    if (r > 0)
        dev->dstats.rx += r;
    return r;
}
static ssize_t pipe_sendto(struct pipe_device *pipe, const void *buffer, size_t size)
{
    struct sockaddr_un addr;
    const char *addrname;

    if (pipe->cfg.rpath[0] != '\0')
        addrname = pipe->cfg.rpath;
    else if (pipe->peer.peer_name[0] != '\0' || pipe->peer.peer_name[1] != '\0')
        addrname = pipe->peer.peer_name;
    else
        return -EIO;

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    memcpy(addr.sun_path, addrname, sizeof(addr.sun_path));
    return sendto(dev_fd(&pipe->dev), buffer, size, 0, (struct sockaddr *)&addr,
                  sizeof(addr));
}
static ssize_t pipe_write(device_t *dev, const void *buffer, size_t size)
{
    struct pipe_device *pipe = to_pipe_dev(dev);
    ssize_t r;

    r = pipe_sendto(pipe, buffer, size);
    if (r > 0)
        dev->dstats.tx += r;
    return r;
}
static void pipe_release(device_t *dev)
{
    struct pipe_device *pipe = to_pipe_dev(dev);

    if (pipe->cfg.lpath[0] != '\0')
        unlink(pipe->cfg.lpath);
}
static const struct device_ops pipe_ops =
{
    .open  = pipe_open,
    .ctrl  = pipe_ctrl,
    .read  = pipe_read,
    .peek  = pipe_read,
    .write = pipe_write,
    .release = pipe_release,
};
int pipe_dev_add(const char *name, const struct pipe_cfg *cfg)
{
    struct pipe_device *pipe;
    int r;

    pipe = kcalloc(1, sizeof(struct pipe_device));
    if (!pipe)
        return -ENOMEM;

    if (cfg)
        memcpy(&pipe->cfg, cfg, sizeof(*cfg));
    pipe->dev.ops = &pipe_ops;

    r = device_register(&pipe->dev, name, 0);
    if (r < 0)
        kfree(pipe);
    return r;
}
void pipe_dev_del(device_t *dev)
{
    struct pipe_device *pipe = to_pipe_dev(dev);

    device_unregister(&pipe->dev);
    kfree(pipe);
}
